home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Tricks of the Mac Game Programming Gurus
/
TricksOfTheMacGameProgrammingGurus.iso
/
More Source
/
C⁄C++
/
CopyBits Demo 1.5
/
CopyBits Demo.c
< prev
next >
Wrap
C/C++ Source or Header
|
1995-05-18
|
34KB
|
1,217 lines
//
// CopyBits Demo.c
//
// Written in May 1995 using Metrowerks CodeWarrior v5.
//
// The CopyBits Demo project v1.5 by Kenneth Worley.
// This code is Copyright 1995. All Rights Reserved.
// You may use this code in any project of your own. You may also
// redistribute this project to anyone else as long as 1) all the
// project files (including documentation files) are kept together,
// and 2) nothing is charged for the project.
//
// Please ask my permission before including this code on any
// disk or CD for sale.
//
// This is a tutorial project that demonstrates the use of
// CopyBits and offscreen Graphics Worlds (GWorlds). It shows
// several instances of copying images to and from offscreen
// graphics worlds using CopyBits, provides several macros that
// make the process a little easier, shows an example of
// drawing into an offscreen graphics world and how that can
// improve onscreen animation, and it demonstrates a "fade"
// using CopyBits to fade a portion of the screen to black.
//
// Version 1.5 adds a fade from image to image using CopyBits.
// Cool!
//
// In a nutshell, the app puts up a dialog that shows two
// "source" pictures (1 & 2) and a destination area. Clicking
// on the "Copy" buttons above each of the source pictures
// causes the picture to be copied to the destination area.
// Picture 1 is labelled as "flickery animation" and picture 2
// is labelled as "smooth animation." When you put the cursor
// over each picture, you should see a difference in how a
// colored circle is animated over the picture. You should
// occasionally see the background "flicker" through on
// picture 1, but not on picture 2 because it uses an
// intermediary GWorld to draw in.
//
// The Fade to black button over the destination area will
// cause that area to fade to black from whatever happens to
// be in there. It does this by repeatedly copying a gray
// rectangle from a GWorld into the destination area using
// the subPin transfer mode of CopyBits. The erase button
// erases the destination area.
//
// The Fade picture buttons cause the picture under the
// button to be faded into whatever is currently in the
// destination area. This is accomplished using CopyBits'
// blend transfer mode.
//
// Questions? Comments? Praise? Criticism? Jobs? Send them
// all to me at KNEworley@aol.com. I'm a 'freelance'
// programmer. I work on applications as well as low-level
// software (control panels, INITs, etc.)
//
#include <QDOffscreen.h>
// ===================== DEFINES =====================
#define kMainDlgID 128
#define kPict1ID 128
#define kPict2ID 129
#define kMainDlgQuit 1
#define kMainDlgCopyOne 2
#define kMainDlgFadeOne 3
#define kMainDlgCopyTwo 4
#define kMainDlgFadeTwo 5
#define kMainDlgErase 6
#define kMainDlgFadeToBlack 7
#define kMainDlgPICTOne 8
#define kMainDlgPICTTwo 9
#define kMainDlgDestPICT 10
#define kMainDlgFadeSpeed 15
#define kSys7DlgID 129
#define kMemoryOutDlgID 130
#define kGWorldErrDlgID 131
#define kNotYetImplDlgID 132
#define kAlertOKButton 1
// ===================== MACROS =====================
// w can be a WindowPtr, a DialogPtr, or a GWorldPtr. In any of these
// cases, it returns the correct "bitmap" to send to CopyBits in the
// source or destination bitmap parameter.
#define WINBITMAP(w) ((((WindowPeek)(w))->port).portBits)
// w can also be a WindowPtr, DialogPtr, or GWorldPtr for any of these.
#define WINPORTRECT(w) ((((WindowPeek)(w))->port).portRect)
#define WINDOWWIDTH(w) (WINPORTRECT(w).right - WINPORTRECT(w).left)
#define WINDOWHEIGHT(w) (WINPORTRECT(w).bottom - WINPORTRECT(w).top)
#define WINCONTENTRECT(w) ((**((WindowPeek)(w))->contRgn).rgnBBox)
#define WINCONTENTRGN(w) (((WindowPeek)(w))->contRgn)
#define WINVISIBLERGN(w) (((WindowPeek)(w))->port.visRgn)
#define WINSTRUCTRECT(w) ((**((WindowPeek)(w))->strucRgn).rgnBBox)
#define WINSTRUCTRGN(w) (((WindowPeek)(w))->strucRgn)
#define WINUPDATERECT(w) ((**((WindowPeek)(w))->updateRgn).rgnBBox)
#define WINUPDATERGN(w) (((WindowPeek)(w))->updateRgn)
// This takes a GDHandle (device handle)
#define PIXELDEPTH(x) ((**((**(x)).gdPMap)).pixelSize)
// r is a Rect
#define TOPLEFT(r) (* (Point*) &(r.top) )
#define BOTRIGHT(r) (* (Point*) &(r.bottom) )
// ===================== PROTOTYPES =====================
void main( void );
void InitToolbox( void );
short GetSysVersion( void );
Boolean HasColorQuickDraw( void );
Handle GetItemHandle( DialogPtr dlg, short itemNo );
void GetItemRect( DialogPtr dlg, short itemNo, Rect *aRect );
long GetDialogNumberField( DialogPtr dlg, short itemNo );
void DoAlert( short dlgID, Boolean playAlert );
GDHandle WindowDevice( WindowPtr winPtr );
void FadeToBlack( WindowPtr destWin, Rect *destRect, short fadeSpeed );
void FadeToImage( GWorldPtr srcImage, Rect *srcRect,
WindowPtr destWin, Rect *destRect, short fadeSpeed );
void FlickerAnimate( GWorldPtr srcPict, Rect *destRect );
void SmoothAnimate( GWorldPtr srcPict, Rect *destRect );
// ===================== FUNCTIONS =====================
void main( void )
{
DialogPtr mainDlg;
DialogPtr aDlg;
Rect pict1Rect;
Rect pict2Rect;
Rect destRect;
PenState savedPen;
short itemHit;
RGBColor savedFore;
RGBColor savedBack;
RGBColor blackColor;
RGBColor whiteColor;
short fadeSpeed;
OSErr myErr;
Rect pict1GRect;
Rect pict2GRect;
GWorldPtr pict1GWorld;
GWorldPtr pict2GWorld;
EventRecord myEvent;
// Make colors for use later.
blackColor.red = blackColor.green = blackColor.blue = 0;
whiteColor.red = whiteColor.green = whiteColor.blue = 0xFFFF;
// Initialize the Mac Toolbox
InitToolbox();
// Check the system version. This project requires System 7
// or later for the GWorlds.
if ( GetSysVersion() < 7 )
{
DoAlert( kSys7DlgID, true );
ExitToShell();
}
// Load the main (only) dialog and display it on screen. This
// automatically displays our two source PICTS. Since there is
// a 'dctb' (Dialog Color Table) resource with the same ID
// as the dialog, the Dialog Manager uses NewColorDialog to
// make the dialog, thus giving us a color drawing port.
mainDlg = GetNewDialog( kMainDlgID, NULL, (WindowPtr)(-1L) );
if ( !mainDlg )
{
DoAlert( kMemoryOutDlgID, true );
ExitToShell();
}
// Select the text in the fade speed text edit box.
SelectDialogItemText( mainDlg, kMainDlgFadeSpeed, 0, 255 );
// Make sure the dialog is visible.
ShowWindow( mainDlg );
SetGWorld( (CGrafPtr)mainDlg, NULL );
PenNormal();
// Get the picture rectangles and the destination picture's
// rectangle. Save them for later use.
GetItemRect( mainDlg, kMainDlgPICTOne, &pict1Rect );
GetItemRect( mainDlg, kMainDlgPICTTwo, &pict2Rect );
GetItemRect( mainDlg, kMainDlgDestPICT, &destRect );
// Draw a box around each of the picture areas.
{
Rect tempRect;
tempRect = destRect;
InsetRect( &tempRect, -1, -1 );
FrameRect( &tempRect );
tempRect = pict1Rect;
InsetRect( &tempRect, -1, -1 );
FrameRect( &tempRect );
tempRect = pict2Rect;
InsetRect( &tempRect, -1, -1 );
FrameRect( &tempRect );
}
// Draw each of the two "source" pictures into an offscreen
// Graphics World for use later. For example, when the user
// passes the cursor over the left picture, regular animation
// techniques are used to make a circle "float" above the
// picture (causes flicker). This involves repeatedly
// copying the original picture to the window, then drawing
// the circle over it. When the user passes the cursor over
// the right picture, the original picture is drawn to an
// offscreen gworld and the circle is drawn over it, then
// that image is drawn to the window resulting in flicker-free
// animation of the floating circle.
//
// We need the two gworlds here so we have the original
// pictures to copy from.
// Convert the destination rectangles into global coordinates
pict1GRect = pict1Rect;
LocalToGlobal( &TOPLEFT(pict1GRect) );
LocalToGlobal( &BOTRIGHT(pict1GRect) );
pict2GRect = pict2Rect;
LocalToGlobal( &TOPLEFT(pict2GRect) );
LocalToGlobal( &BOTRIGHT(pict2GRect) );
// Create offscreen GWorlds with the same size and depth as the
// destination rectangles. The parameters are as follows:
// &pictxGWorld ptr to new graphics world
// 0 bit depth same as graphics device of dest rect
// &pictxGRect bounds rectangle of GWorld
// NULL handle to a color table record - NULL means
// use the default record for that depth
// NULL handle to a graphics device record - we aren't
// creating a new graphics device
// 0L no flags
myErr = NewGWorld( &pict1GWorld, 0, &pict1GRect, NULL, NULL, 0L );
myErr = NewGWorld( &pict2GWorld, 0, &pict2GRect, NULL, NULL, 0L );
// I'm just assuming here that an error means we don't have enough memory.
if ( myErr || !pict1GWorld || !pict2GWorld )
{
DoAlert( kMemoryOutDlgID, true );
ExitToShell();
}
// Draw the pictures in the picture offscreen graphics worlds.
// When I read in the PICT resources, I'm just assuming there won't
// be any errors because they're already loaded by the dialog
// anyway, but you never know. You probably really should check :)
// See the FadeToBlack routine for more explanation on locking and
// unlocking pixels.
{
PicHandle aPict;
PixMapHandle thePixMap;
// get and draw picture 1
thePixMap = GetGWorldPixMap( pict1GWorld );
LockPixels( thePixMap );
SetGWorld( (CGrafPtr)pict1GWorld, NULL );
aPict = GetPicture( kPict1ID );
DrawPicture( aPict, &WINPORTRECT( pict1GWorld ) );
UnlockPixels( thePixMap );
// get and draw picture 2
thePixMap = GetGWorldPixMap( pict2GWorld );
LockPixels( thePixMap );
SetGWorld( (CGrafPtr)pict2GWorld, NULL );
aPict = GetPicture( kPict2ID );
DrawPicture( aPict, &WINPORTRECT( pict2GWorld ) );
UnlockPixels( thePixMap );
// make sure and restore the dialog as the current port
SetGWorld( (CGrafPtr)mainDlg, NULL );
}
// Now wait for the user to press a button in the dialog
itemHit = -1;
while ( itemHit != kMainDlgQuit )
{
WaitNextEvent( everyEvent, &myEvent, GetCaretTime(), NULL );
// Check for disk events (bad disk mount).
if ( (myEvent.what == diskEvt) &&
(HiWord(myEvent.message) != noErr) )
{
Point dlgUpLeftCorner = { 100, 80 }; // ignored in Sys 7
DIBadMount( dlgUpLeftCorner, myEvent.message ); // ignore result
}
// If we get an update event, redraw the squares around the
// picture areas.
else if ( myEvent.what == updateEvt )
{
Rect tempRect;
tempRect = destRect;
InsetRect( &tempRect, -1, -1 );
FrameRect( &tempRect );
tempRect = pict1Rect;
InsetRect( &tempRect, -1, -1 );
FrameRect( &tempRect );
tempRect = pict2Rect;
InsetRect( &tempRect, -1, -1 );
FrameRect( &tempRect );
}
// If the mouse is over one of the pictures, call a routine
// to animate a "floating circle" over the picture. A
// different method is used for each picture.
{
Point mouseLoc;
GetMouse( &mouseLoc );
if ( PtInRect( mouseLoc, &pict1Rect ) )
FlickerAnimate( pict1GWorld, &pict1Rect );
else if ( PtInRect( mouseLoc, &pict2Rect ) )
SmoothAnimate( pict2GWorld, &pict2Rect );
}
// Pass the event to DialogSelect which takes care of tracking
// controls and updating everything (except the destination
// area) for us.
if ( DialogSelect( &myEvent, &aDlg, &itemHit ) )
{
// What we do here depends on what the user clicked
switch( itemHit )
{
case kMainDlgCopyOne:
// Save the pen state and foreground/background colors
GetPenState( &savedPen );
GetForeColor( &savedFore );
GetBackColor( &savedBack );
// Set foreground color to black, background color to white
RGBForeColor( &blackColor );
RGBBackColor( &whiteColor );
// Just use CopyBits to copy the PICT. We're actually
// copying from one place on the dialog to another here.
CopyBits( &WINBITMAP( mainDlg ), &WINBITMAP( mainDlg ),
&pict1Rect, &destRect, srcCopy, NULL );
// Restore the saved pen state and colors
SetPenState( &savedPen );
RGBForeColor( &savedFore );
RGBBackColor( &savedBack );
break;
case kMainDlgFadeOne:
// Select the fade speed.
SelectDialogItemText( mainDlg, kMainDlgFadeSpeed, 0, 255 );
// Get the fade speed from the dialog.
fadeSpeed = GetDialogNumberField( mainDlg,
kMainDlgFadeSpeed );
// Call FadeToImage to fade picture 1 into the
// destination area.
FadeToImage( pict1GWorld, &WINPORTRECT( pict1GWorld ),
mainDlg, &destRect, fadeSpeed );
break;
case kMainDlgCopyTwo:
// Save the pen state and foreground/background colors
GetPenState( &savedPen );
GetForeColor( &savedFore );
GetBackColor( &savedBack );
// Set foreground color to black, background color to white
RGBForeColor( &blackColor );
RGBBackColor( &whiteColor );
// Just use CopyBits to copy the PICT from one place in
// the window to another.
CopyBits( &WINBITMAP( mainDlg ), &WINBITMAP( mainDlg ),
&pict2Rect, &destRect, srcCopy, NULL );
// Restore the saved pen state and colors
SetPenState( &savedPen );
RGBForeColor( &savedFore );
RGBBackColor( &savedBack );
break;
case kMainDlgFadeTwo:
// Select the fade speed.
SelectDialogItemText( mainDlg, kMainDlgFadeSpeed, 0, 255 );
// Get the fade speed from the dialog.
fadeSpeed = GetDialogNumberField( mainDlg,
kMainDlgFadeSpeed );
// Call FadeToImage to fade picture 2 into the
// destination area.
FadeToImage( pict2GWorld, &WINPORTRECT( pict2GWorld ),
mainDlg, &destRect, fadeSpeed );
break;
case kMainDlgErase:
// Erase the destination area.
EraseRect( &destRect );
break;
case kMainDlgFadeToBlack:
// Select the fade speed.
SelectDialogItemText( mainDlg, kMainDlgFadeSpeed, 0, 255 );
// Get the fade speed from the dialog.
fadeSpeed = GetDialogNumberField( mainDlg,
kMainDlgFadeSpeed );
// And call FadeToBlack to fade the destination rectangle.
FadeToBlack( mainDlg, &destRect, fadeSpeed );
break;
case kMainDlgQuit:
break;
}
}
}
}
void InitToolbox( void )
{
/* Initialize the Mac Toolbox Managers */
InitGraf((Ptr) &qd.thePort);
InitFonts();
InitWindows();
InitMenus();
FlushEvents(everyEvent,0);
TEInit();
InitDialogs(0L);
InitCursor();
}
// GetSysVersion returns 7 if the system version is 7 or later,
// 6 if it's 6 or later (but not 7), and 0 if earlier than 6.
//
short GetSysVersion( void )
{
SysEnvRec env;
SysEnvirons( 2, &env );
if ( env.systemVersion >= 0x0700 )
return 7;
else if ( env.systemVersion >= 0x0600 )
return 6;
else
return 0;
}
// HasColorQuickDraw returns true if the Mac has color
// QuickDraw.
//
Boolean HasColorQuickDraw( void )
{
SysEnvRec env;
SysEnvirons( 2, &env );
return env.hasColorQD;
}
// GetItemHandle returns a handle to a dialog item given the
// dialog ptr and the item number.
//
Handle GetItemHandle( DialogPtr dlg, short itemNo )
{
short itemType;
Handle itemHandle;
Rect itemRect;
GetDialogItem( dlg, itemNo, &itemType, &itemHandle, &itemRect );
return itemHandle;
}
// GetItemRect returns the rectangle of a dialog item (in local
// coordinates) given the dialog ptr, and the item number.
//
void GetItemRect( DialogPtr dlg, short itemNo, Rect *aRect )
{
short itemType;
Handle itemHandle;
Rect itemRect;
GetDialogItem( dlg, itemNo, &itemType, &itemHandle, &itemRect );
(*aRect) = itemRect;
}
// GetDialogNumberField gets the text in the field specified by itemNo
// in the dialog specified by dlg and converts it into a number. The
// number is returned as the function result.
//
long GetDialogNumberField( DialogPtr dlg, short itemNo )
{
Str255 theStr;
long theNum;
GetDialogItemText( GetItemHandle( dlg, itemNo ), theStr );
StringToNum( theStr, &theNum );
return theNum;
}
void DoAlert( short dialogID, Boolean playAlert )
{
/* This routine displays an alert whose resource ID number is specified
* in the parameter dialogID. The dialog specified is expected to have
* at least one button (usually labeled "OK") which will be the default
* button and have an ID if 1. When the user presses this button, the
* alert will be dismissed.
*/
WindowPtr currentPort; /* store the current port here */
DialogPtr theDlg; /* to store our dialog in */
short itemType; /* these 3 local variables are used to */
Handle itemHandle; /* manipulate items in the dialog. */
Rect itemRect;
short itemHit; /* use with ModalDialog */
// Save the current grafPort
GetPort( ¤tPort );
// Now, load our dialog resource and display it.
// load the dialog resource
theDlg = GetNewDialog( dialogID, NULL, (WindowPtr)-1L );
// make the dialog the current port
SetPort( theDlg );
// if the parameter specifies, do a system beep
if ( playAlert )
SysBeep( 3 );
// now make sure the dialog is visible
ShowWindow( theDlg );
/* Get the OK button item and draw a bold border around it to show that
* it's the default button.
*/
GetDItem( theDlg, kAlertOKButton, &itemType, &itemHandle, &itemRect );
InsetRect( &itemRect, -4, -4 );
PenSize( 3, 3 );
FrameRoundRect( &itemRect, 16, 16 );
PenSize( 1, 1 );
// Loop and call ModalDialog until the user presses the OK button
// This routine requires that whatever alert is shown, the button with
// id number kAlertOKButton is the one that should dismiss the dialog.
ModalDialog( NULL, &itemHit );
while ( itemHit != kAlertOKButton )
ModalDialog( NULL, &itemHit );
/* Now get rid of the dialog and set the port back to the control panel */
DisposDialog( theDlg );
SetPort( currentPort );
}
// WindowDevice returns a handle to the graphics device record of
// the monitor that the window specified is displayed on.
//
GDHandle WindowDevice( WindowPtr winPtr )
{
GDHandle theDevice;
Point targetPt;
targetPt = TOPLEFT( ((WindowPeek)winPtr)->port.portRect );
theDevice = GetDeviceList();
while ( !PtInRect( targetPt, &((**theDevice).gdRect) ) )
{
theDevice = GetNextDevice( theDevice );
if ( theDevice == NULL )
{
theDevice = GetMainDevice();
break;
}
}
return theDevice;
}
// FadeToBlack uses CopyBits and an offscreen graphics world to
// fade an area (specified by destRect) in a window (specified
// by destWin) to black. It does this by creating an offscreen
// graphics world, painting the GWorld gray, then repeatedly
// copying the gray rectangle to the destination area using the
// subPin transfer method of CopyBits. This causes the destination
// to grow darker and darker.
//
void FadeToBlack( WindowPtr destWin, Rect *destRect, short fadeSpeed )
{
CGrafPtr savedPort;
GDHandle savedDevice;
GWorldPtr offscreenWorld;
Rect offscreenRect;
OSErr myErr;
PenState savedPen;
RGBColor savedFore;
RGBColor savedBack;
PixMapHandle thePixMap;
long repetitions;
RGBColor grayColor;
RGBColor blackColor;
RGBColor whiteColor;
// Make the black and white colors
blackColor.blue = blackColor.green = blackColor.red = 0;
whiteColor.blue = whiteColor.green = whiteColor.red = 0xFFFF;
// Save the current port/device. This should be used instead
// of GetPort. Though savedPort is defined as a CGrafPtr,
// the routine may return a GrafPtr or pointer to a GWorld
// instead (GWorldPtr).
GetGWorld( &savedPort, &savedDevice );
// Set the current port to the destination window
SetGWorld( (CGrafPtr)destWin, NULL );
// Save the pen state and color info of the destination window
GetPenState( &savedPen );
GetForeColor( &savedFore );
GetBackColor( &savedBack );
// Make sure the foreground color of the destination window
// is black and the background color is white.
RGBForeColor( &blackColor );
RGBBackColor( &whiteColor );
// Convert the destination rectangle into global coordinates
offscreenRect = (*destRect);
LocalToGlobal( &TOPLEFT(offscreenRect) );
LocalToGlobal( &BOTRIGHT(offscreenRect) );
// Make the offscreen rectangle smaller. It doesn't matter since it's
// all the same color and it'll use up less memory.
offscreenRect.right = offscreenRect.left + 2;
offscreenRect.bottom = offscreenRect.top + 2;
// Create an offscreen GWorld with the same depth as the
// destination window/rectangle. The parameters are as follows:
// &offscreenWorld ptr to new graphics world
// 0 bit depth same as graphics device of dest rect
// &offscreenRect bounds rectangle of my GWorld
// NULL handle to a color table record - NULL means
// use the default record for that depth
// NULL handle to a graphics device record - we aren't
// creating a new graphics device
// 0L no flags
myErr = NewGWorld( &offscreenWorld, 0, &offscreenRect, NULL, NULL, 0L );
// Make sure the GWorld was created
if ( myErr )
{
DoAlert( kGWorldErrDlgID, true );
ExitToShell();
}
// Make the GWorld the current port
SetGWorld( offscreenWorld, NULL );
// Get the GWorld's pixel map handle and lock the pixels while we're
// drawing in the GWorld. We have to do this because a GWorld actually
// holds its pixel map in a handle rather than a pointer. LockPixels
// makes sure the pixel map doesn't move. You should lock a GWorld's
// pixels before drawing to or copying from the GWorld (and unlock
// them afterward).
thePixMap = GetGWorldPixMap( offscreenWorld );
LockPixels( thePixMap );
// The speed of the fade determines what shade of gray we'll paint
// the offscreen GWorld. fadeSpeed can range from 0 to 20 with 20
// being the fastest.
{
unsigned short maxValue=0xFFFF;
if ( fadeSpeed <= 0 ) fadeSpeed = 1; // make sure speed is not zero
if ( fadeSpeed > 20 ) fadeSpeed = 20; // make sure not over 20
grayColor.blue = (fadeSpeed * 625) + 8000;
grayColor.red = grayColor.green = grayColor.blue;
RGBForeColor( &grayColor );
repetitions = maxValue / grayColor.blue;
}
// Paint it all gray
PaintRect( &(offscreenWorld->portRect) );
// Make the destination window the current port
SetGWorld( (CGrafPtr)destWin, NULL );
// Loop and use CopyBits to copy the gray rectangle into the
// destination window/rect using the subPin transfer method
// so that the picture gets darker and darker.
while ( repetitions )
{
CopyBits( &WINBITMAP( offscreenWorld ), &WINBITMAP( destWin ),
&(offscreenWorld->portRect), destRect, subPin, NULL );
repetitions--;
}
// Make the GWorld the current port
SetGWorld( offscreenWorld, NULL );
// Now we'll do a fast fade just to make sure everything went completely
// to black.
{
unsigned short maxValue=0xFFFF;
grayColor.blue = 20500;
grayColor.red = grayColor.green = grayColor.blue;
RGBForeColor( &grayColor );
repetitions = maxValue / grayColor.blue + 1;
}
// Paint it all gray
PaintRect( &(offscreenWorld->portRect) );
// Make the destination window the current port
SetGWorld( (CGrafPtr)destWin, NULL );
// Loop and use CopyBits to copy the gray rectangle into the
// destination window/rect using the subPin transfer method
// so that the picture gets darker and darker.
while ( repetitions )
{
CopyBits( &WINBITMAP( offscreenWorld ), &WINBITMAP( destWin ),
&(offscreenWorld->portRect), destRect, subPin, NULL );
repetitions--;
}
// Unlock the pixels again.
UnlockPixels( thePixMap );
// Restore the saved port/device
SetGWorld( savedPort, savedDevice );
// Restore the pen state and color info of the destination window
SetPenState( &savedPen );
RGBForeColor( &savedFore );
RGBBackColor( &savedBack );
// Dispose of the GWorld
DisposeGWorld( offscreenWorld );
}
// FadeToImage takes the source image and does a smooth fade in
// the destination window and rect from the image that is currently
// there to the source image. This is done using CopyBits with
// the "blend" transfer mode.
//
void FadeToImage( GWorldPtr srcImage, Rect *srcRect,
WindowPtr destWin, Rect *destRect, short fadeSpeed )
{
CGrafPtr savedPort;
GDHandle savedDevice;
RGBColor savedFore, savedBack;
PenState savedPen;
RGBColor grayColor;
RGBColor blackColor;
RGBColor whiteColor;
float speed;
unsigned short lastColor;
// Make sure speed is within our limits and set it appropriately.
// We want to end up with a range of 1.2 to 3.2 from the original
// 0 to 20 range.
if ( fadeSpeed < 1 ) speed = 1.2;
else if ( fadeSpeed > 20 ) speed = 3.2;
else speed = 1.2 + (fadeSpeed/10);
// Make the black and white colors
blackColor.blue = blackColor.green = blackColor.red = 0;
whiteColor.blue = whiteColor.green = whiteColor.red = 0xFFFF;
// Save the current port and device
GetGWorld( &savedPort, &savedDevice );
// Set the port to the destination window and save its
// pen state and fore/background color values.
SetGWorld( (CGrafPtr)destWin, NULL );
GetPenState( &savedPen );
GetForeColor( &savedFore );
GetBackColor( &savedBack );
// Make sure the port's fore and background colors are set
// correctly for CopyBits to work correctly.
RGBForeColor( &blackColor );
RGBBackColor( &whiteColor );
// Lock the pixels in the source image. This is required
// when copying from a GWorld.
LockPixels( GetGWorldPixMap( srcImage ) );
// Set the beginning blend weight. This will be multiplied by
// speed before being used the first time.
grayColor.blue = grayColor.green = grayColor.red = 0x0600;
lastColor = 0;
// Loop and copy the source image to the destination
// blending more and more of the source image in by using
// a lighter and lighter "blend weight" (set by the OpColor
// function).
while ( grayColor.blue < 0xFFFF )
{
// Lighten the blend weight color.
grayColor.blue *= speed;
// Make sure the number didn't roll over.
if ( grayColor.blue < lastColor )
grayColor.blue = 0xFFFF;
grayColor.red = grayColor.green = grayColor.blue;
// Remember the number for next time.
lastColor = grayColor.blue;
// Set the new blend weight.
OpColor( &grayColor );
// copy the image
CopyBits( &WINBITMAP( srcImage ),
&WINBITMAP( destWin ),
srcRect, destRect, blend, NULL );
}
// Finally, just copy the source image to the destination
// window unchanged to make sure the transformation is
// complete.
CopyBits( &WINBITMAP( srcImage ),
&WINBITMAP( destWin ),
srcRect, destRect, srcCopy, NULL );
// Reset the blend weight to black.
// This is the "normal" weight.
OpColor( &blackColor );
// Unlock the pixels in the source image.
UnlockPixels( GetGWorldPixMap( srcImage ) );
// Restore the destination window's properties.
SetPenState( &savedPen );
RGBForeColor( &savedFore );
RGBBackColor( &savedBack );
// Restore the current port and device.
SetGWorld( savedPort, savedDevice );
}
// FlickerAnimate animates a colored circle floating over
// a picture at the mouse location. It draws directly to
// the screen resulting in a circle that the background
// occasionally "flickers" through. Drawing will occur in
// the current port.
//
void FlickerAnimate( GWorldPtr srcPict, Rect *destRect )
{
Point mouseLoc;
Rect mouseRect;
RGBColor savedFore, savedBack;
RGBColor blackColor, whiteColor;
PixMapHandle thePixMap;
RgnHandle savedClip;
RGBColor redColor;
long ignore;
// Make black and white colors.
blackColor.blue = blackColor.green = blackColor.red = 0;
whiteColor.blue = whiteColor.green = whiteColor.red = 0xFFFF;
// Make the color to draw the circle with.
redColor.blue = redColor.green = 0;
redColor.red = 0xFFFF;
// Save fore and background colors and make them
// fore-black and back-white.
GetForeColor( &savedFore ); GetBackColor( &savedBack );
RGBForeColor( &blackColor ); RGBBackColor( &whiteColor );
// Save the clipping region.
savedClip = NewRgn();
GetClip( savedClip );
// Set the clipping region to just cover the picture.
ClipRect( destRect );
// Lock the pixel map of the src GWorld while copying from it.
thePixMap = GetGWorldPixMap( srcPict );
LockPixels( thePixMap );
// Loop and draw the picture and circle repeatedly.
do
{
// Get a mouse location.
GetMouse( &mouseLoc );
// Make a square around the mouse location to draw
// the circle in.
mouseRect.left = mouseRect.right = mouseLoc.h;
mouseRect.top = mouseRect.bottom = mouseLoc.v;
InsetRect( &mouseRect, -20, -20 );
// Copy the picture to the window.
CopyBits( &WINBITMAP( srcPict ),
&WINBITMAP( FrontWindow() ),
&WINPORTRECT( srcPict ),
destRect, srcCopy, NULL );
// Draw the circle at the mouse location.
RGBForeColor( &redColor );
PaintOval( &mouseRect );
RGBForeColor( &blackColor );
// Delay for a fraction of a second here. If we
// don't, the flickering is really, really bad.
Delay( 2, &ignore );
} while ( PtInRect( mouseLoc, destRect ) );
// Draw the picture one more time to make sure the
// circle is erased.
CopyBits( &WINBITMAP( srcPict ),
&WINBITMAP( FrontWindow() ),
&WINPORTRECT( srcPict ),
destRect, srcCopy, NULL );
// Unlock the GWorld's pixel map.
UnlockPixels( thePixMap );
// Restore the clipping region.
SetClip( savedClip );
// Restore the saved fore and background colors.
RGBForeColor( &savedFore );
RGBBackColor( &savedBack );
}
// SmoothAnimate animates a colored circle floating over
// a picture at the mouse location. It draws to a GWorld
// then copies the GWorld to the screen. This results in
// flicker-free animation where the background never
// shows through the floating circle.
//
void SmoothAnimate( GWorldPtr srcPict, Rect *destRect )
{
Point mouseLoc;
Rect mouseRect;
RGBColor savedFore, savedBack;
RGBColor blackColor, whiteColor;
PixMapHandle srcPixMap, midPixMap;
GWorldPtr middleMan;
CGrafPtr thisWindow;
GDHandle thisDevice;
RGBColor purpleColor;
OSErr myErr;
// Save the current window and device (this is the window
// we're drawing into.)
GetGWorld( &thisWindow, &thisDevice );
// Make black and white colors.
blackColor.blue = blackColor.green = blackColor.red = 0;
whiteColor.blue = whiteColor.green = whiteColor.red = 0xFFFF;
// Make the color to draw the circle with.
purpleColor.blue = purpleColor.red = 0xFFFF;
purpleColor.green = 0;
// Save fore and background colors and make them
// fore-black and back-white.
GetForeColor( &savedFore ); GetBackColor( &savedBack );
RGBForeColor( &blackColor ); RGBBackColor( &whiteColor );
// Make a GWorld to draw the picture and circle into. The
// onscreen image will be copied from this "middle man."
// It is the same size and depth as the destination
// on screen.
// &middleMan ptr to new graphics world
// 0 bit depth same as graphics device of dest rect
// destRect bounds rectangle of new GWorld
// NULL handle to a color table record - NULL means
// use the default record for that depth
// NULL handle to a graphics device record - we aren't
// creating a new graphics device
// 0L no flags
myErr = NewGWorld( &middleMan, 0, destRect, NULL, NULL, 0L );
// Make sure the GWorld was created
if ( myErr || !middleMan )
{
DoAlert( kGWorldErrDlgID, true );
ExitToShell();
}
// Lock the pixel map of the src GWorld and the middleMan
// GWorld while copying from them.
srcPixMap = GetGWorldPixMap( srcPict );
LockPixels( srcPixMap );
midPixMap = GetGWorldPixMap( middleMan );
LockPixels( midPixMap );
// Loop and draw the pict and the circle repeatedly.
do
{
// Get a mouse location and translate into coordinates
// local to the middleMan GWorld.
GetMouse( &mouseLoc );
mouseLoc.h -= destRect->left;
mouseLoc.v -= destRect->top;
// Make a square around the mouse location to draw
// the circle in.
mouseRect.left = mouseRect.right = mouseLoc.h;
mouseRect.top = mouseRect.bottom = mouseLoc.v;
InsetRect( &mouseRect, -20, -20 );
// Set the current port to middleMan.
SetGWorld( (CGrafPtr)middleMan, NULL );
// Copy the picture to the middleMan GWorld.
CopyBits( &WINBITMAP( srcPict ),
&WINBITMAP( middleMan ),
&WINPORTRECT( srcPict ),
&WINPORTRECT( middleMan ), srcCopy, NULL );
// Draw the circle at the mouse location.
RGBForeColor( &purpleColor );
PaintOval( &mouseRect );
RGBForeColor( &blackColor );
// Reset the current port to the destination window.
SetGWorld( thisWindow, thisDevice );
// Now copy the middleMan image to the destination
// window on screen.
CopyBits( &WINBITMAP( middleMan ),
&WINBITMAP( thisWindow ),
&WINPORTRECT( middleMan ),
destRect, srcCopy, NULL );
} while ( PtInRect( mouseLoc, &WINPORTRECT( middleMan ) ) );
// Draw the picture one more time to make sure the
// circle is erased.
CopyBits( &WINBITMAP( srcPict ),
&WINBITMAP( FrontWindow() ),
&WINPORTRECT( srcPict ),
destRect, srcCopy, NULL );
// Unlock the GWorld's and middleMan's pixel maps.
UnlockPixels( srcPixMap );
UnlockPixels( midPixMap );
// Dispose of the middleMan GWorld.
DisposeGWorld( middleMan );
// Restore the saved fore and background colors.
RGBForeColor( &savedFore );
RGBBackColor( &savedBack );
}